package com.lime49.lockcrypt;
import java.util.*;
import org.json.*;

public class AccType implements Comparable, Cloneable {
    private String name, notes;
    public String[] fields;  // used when this object is a template
    public LinkedHashMap<String, String> fieldmap; // used when this object holds info for an account
    public boolean isTemplate, isChanged, isDeleted = false, showNotes, isProtected;
    private String ID;
    
    /**
     * Creates a dummy account type to use on the add type dialog
     */
    public AccType() {
        this.name = "";
        this.fields = new String[] {""};
        this.isTemplate = true;
        this.isChanged = true;
        this.isProtected = false;
        this.showNotes = true;
        this.notes = "";
        this.ID = Utils.GenID(true);
    }
    
    /**
     * Used when this object holds a template for an account type
     * @param name The name of this type
     * @param fields The fields for this account type
     * @param isChanged Whether or not the account has been changed since the database was loaded
     * @param showNotes Whether or not to show the notes field for this account type
     */
    public AccType(String name, String[] fields, boolean isChanged, boolean isProtected, boolean showNotes) {
        this.name = name;
        this.fields = fields;
        this.isTemplate = true;
        this.isChanged = isChanged;
        this.isProtected = isProtected;
        this.showNotes = showNotes;
        this.fieldmap = new LinkedHashMap<String, String>();
        for(String field : fields) {
            this.fieldmap.put(field, "");
        }
        this.ID = Utils.GenID(true);
    }
    
    /**
     * Used when this object hold the hashmap of fields<>fieldvals for an account
     * @param name The name of this account type
     * @param fieldmap A LinkedHashMap of the account fields
     * @param showNotes Whether or not to show the notes field for this account
     * @param notes The notes for this account
     */
    public AccType(String name, LinkedHashMap<String, String> fieldmap, boolean showNotes, String notes) {
        this.name = name;
        this.fieldmap = fieldmap;
        this.isTemplate = false;
        this.showNotes = showNotes;
        this.notes = notes.replace("_N_", "\n");
        this.ID = Utils.GenID(true);
    }
    
    /**
     * Used when this object hold the hashmap of fields<>fieldvals for an account
     * @param ID The ID of this Type
     * @param name The name of this account type
     * @param fieldmap A LinkedHashMap of the account fields
     * @param showNotes Whether or not to show the notes field for this account
     * @param notes The notes for this account
     */
    public AccType(String ID, String name, LinkedHashMap<String, String> fieldmap, boolean showNotes, String notes) {
        this.ID = ID;
        this.name = name;
        this.fieldmap = fieldmap;
        this.isTemplate = false;
        this.showNotes = showNotes;
        this.notes = notes.replace("_N_", "\n");
    }
    
    /**
     * Returns an array of the fields for this account type
     * @return The fields for this account type
     */
    public String[] getFields() {
        return fields;
    }
    
    /**
    * Returns the name of this type
    * @return The name of this type
    */
    public String getName() {
        return name;
    }
    
    /**
     * Returns the value stored for the specified field
     * @param name The name of the field to return
     * @param nullIfNotExist If True, returns null if the field does not exist. If False, returns an empty string
     * @return The value associated with this field
     */
    public String getField(String name, boolean nullIfNotExist) {
        Set<String> keys = fieldmap.keySet();
        //for(int i=0;i<keys.size();i++) {
        for(String key : keys) {
            if(key.equals(name)) {
                return fieldmap.get(name);
            } else if(key.equals("~}*"+name)) {
                return fieldmap.get("~}*"+name);
            } else if(key.equals("~}#"+name)) {
                return fieldmap.get("~}#"+name);
            } else if(key.equals("~}!"+name)) {
                return fieldmap.get("~}!"+name);
            } else if(key.equals("~}u"+name)) {
                return fieldmap.get("~}u"+name);
            }
        }
        if(nullIfNotExist) {
            return null;
        } else {
            return new String();
        }
    }
    
    /**
     * Returns the value stored for the specified field
     * @param name The name of the field to return
     * @return The value associated with this field
     */
    public String getField(String name) {
        return this.getField(name, false);
    }

    /**
     * Gets the notes for this account type
     * @return The notes for this account type
     */
    public String getNotes() {
        return notes;
    }
    
    /**
     * Finds whether or not to show the notes field for this account type
     * @return True if the notes field is enabled, otherwise false
     */
    public boolean getShowNotes() {
        return showNotes;
    }
    
    /**
     * Gets the ID of this type
     * @return The ID of this type
     */
    public String getID() {
        return ID;
    }
    
    /**
     * Sets the ID of this type
     * @param id The ID
     */
    public void setID(String id) {
        this.ID = id;
    }
    
    /**
     * Finds whether or not tthis type is protected (cannot be deleted)
     * @return True if the notes field is protected, otherwise false
     */
    public boolean getIsProtected() {
        return isProtected;
    }
    
    /**
     * Sets whether or not this account type should be protected from deletion
     */
    public void setIsProtected(boolean isProtected) {
        this.isProtected = isProtected;
    }
    
    /**
     * Finds whether or not the specified field exists in this account
     * @param name The name of the field to check
     * @return True if the field exists, otherwise false
     */
    public boolean fieldExists(String name) {
        if(fieldmap.containsKey(name) || fieldmap.containsKey("~}*"+name) || fieldmap.containsKey("~}#"+name) || fieldmap.containsKey("~}!"+name) || fieldmap.containsKey("~}u"+name)) {
            return true;
        }
        return false;
    }
    
    /**
     * Returns the LinkedHashMap of field<>value pairs
     * @return LinkedHashMap of fields and value for this account
     */
    public LinkedHashMap<String, String> getFieldMap() {
        return fieldmap;
    }
    
    /**
     * Sets the isChanged and isDeleted flags to true to prevent this account 
     * from being re-synchronised to the database
     */
    public void delete() {
        isDeleted = true;
        isChanged = true;
    }
    
    /**
     * Removes the specified field from the fieldmap for this account
     * @param fieldname The name of the field to remove
     */
    public void removeField(String fieldname) {
        fieldmap.remove(fieldname);
    }
    
    /**
     * Renames the specified field
     * @param oldname The current name of the field
     * @param newname The new name
     */
    public void replaceField(String oldname, String newname) {
        LinkedHashMap<String, String> newmap = new LinkedHashMap<String, String>();
        for(String fieldName : fieldmap.keySet()) {
            if(fieldName.equals(oldname)) { // field found, replace it
                newmap.put(newname, fieldmap.get(fieldName));
            } else if(fieldName.startsWith("~}")) { // field is a phone number or obfuscateed
                if(fieldName.substring(3).equals(oldname)) { // field with meta-data replace the old name with the new one, keeping the meta-data
                    newmap.put(fieldName.substring(0,3)+newname, fieldmap.get(fieldName));
                } else { // not the right field, copy it to the new map
                    newmap.put(fieldName, fieldmap.get(fieldName));
                }
            } else { //  field doesn't match, copy it from the old map to the new one
                newmap.put(fieldName, fieldmap.get(fieldName));
            }
        }
        fieldmap = newmap;
    }
    
    /**
     * Debugging method used to verify contents of this AccType
     * @return A textual representation of this type
     */
    public String fieldMapToString() {
        String ret = "--Account Info--\n"+name+"\n";
        for(String key : fieldmap.keySet()) {
            ret += key+": "+fieldmap.get(key)+"\n";
        }
        return ret;
    }
    
    /**
     * Debugging method used to verify contents of this AccType
     * @return The fields stored in this template
     */
    public String fieldsToString() {
        return Utils.implode(fields, ", ");
    }
    
    /**
    * Used for debugging, returns the name and fields stored in this type
    */
    @Override
    public String toString() {
        if(isTemplate) {
            return name+" (template) - "+fieldsToString(); //+"\n";
        } else {
            return name+" "+fieldMapToString(); //+"\n";
        }
    }
    
    /**
     * Used for file writing
     * @return Returns this AccType as a string for file writing
     */
    public String getOutput() {
        try {
            JSONObject jso = new JSONObject();
            jso.put("Name", getName());
            jso.put("ID", getID());
            jso.put("ShowNotes", getShowNotes());
            jso.put("IsProtected", isProtected);
            jso.put("Fields", new JSONArray(fields));
            String jsonString = "typ"+jso.toString();
            return jsonString;
        } catch(Exception ex) {
            return "";
        }
    }
    
    /**
     * Returns a string containing the name and all fields and values for this type
     * @return A string containing all information associated with this type
     */
    public String getSearchString() {
        return fieldmap.values().toString();
    }
    
    /**
     * Used for file writing
     * @param fields The field array with which to synchronise (order) the output
     * @return Returns this AccType as a string for file writing
     *
    public String getOutput(String[] fields) {
        return Utils.implode(fieldmap,fields,Constants.SEPARATOR);
    }*/
    
    /**
     * Implements Comparable
     * @param otherObj Another AccType
     * @return 
     */
    public int compareTo(Object otherObj) {
        AccType otherType = (AccType)otherObj;
        return name.toLowerCase().compareTo(otherType.getName().toLowerCase());
    }
    
    @Override
    public AccType clone() {
        try {
            return (AccType)super.clone();
        } catch(Exception ex) { return null; }
    }

}
